package trading_strategy.events.history;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.DateUtils;
import org.apache.log4j.Logger;
import trading_strategy.events.MarketEvent;
import trading_strategy.events.OrderBook;
import trading_strategy.events.OrderBookUpdate;
import trading_strategy.events.PublicTradeUpdate;
import trading_strategy.instruments.ListedInstrument;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;

/**
 * Created by IntelliJ IDEA.
 * User: gderou
 * Date: 8 juin 2010
 * Time: 22:28:08
 * To change this template use File | Settings | File Templates.
 */
public class TRTHInstrumentReader {
    double lastBid, lastAsk, lastTradePrice;
    int lastBidQty, lastAskQty, lastTradeQty;

    // Last updates sorted by decreasing timestamp (last arrived is first)
    LinkedList<OrderBookUpdate> lastOrderBookUpdates = new LinkedList<OrderBookUpdate>();
    long nextTimestamp = -1;
    BufferedReader reader;
    String[] lastRead;
    Date lastReadTs;
    ListedInstrument instrument;
    Date nextOpenDate, nextCloseDate;
    boolean open = false;
    private static final Logger LOGGER = Logger.getLogger(TRTHInstrumentReader.class);
    private SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd-HH:mm:ss.SSS");

    public TRTHInstrumentReader(ListedInstrument instrument, String fileName) {
        this.instrument = instrument;
        File file = new File(fileName);
        if (file.exists()) {
            try {
                reader = new BufferedReader(new FileReader(file));
                reader.readLine(); // Skip header
            } catch (IOException e) {
                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            }
        }
    }

    public long nextTimestamp() {
        if (nextTimestamp < 0 && reader != null) {
            try {
                String line = reader.readLine();
                if (line != null) {
                    String[] strings = StringUtils.splitByWholeSeparatorPreserveAllTokens(line, ",");
                    if (strings.length > 1) {
                        String ds = strings[1] + "-" + strings[2];
                        if (nextOpenDate == null) {
                            Date d = DateUtils.parseDate(strings[1], new String[]{"MM/dd/yyyy"});
                            nextOpenDate = instrument.getMarket().getNextOpenDate(d);
                            nextCloseDate = instrument.getMarket().getNextCloseDate(d);
                        }
                        Date ts = DateUtils.parseDate(ds, new String[]{"MM/dd/yyyy-HH:mm:ss.SSSS"});
                        lastReadTs = ts;
                        lastRead = strings;
                        nextTimestamp = lastReadTs.getTime() * 1000l;
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            }
        }
        return nextTimestamp;
    }

    public void readNext() {
        if (nextTimestamp() > 0) {
            if (lastReadTs.after(nextOpenDate)) {
                // Send open event
                instrument.getMarket().getMarketEventProvider().addEvent(new MarketEvent(MarketEvent.MarketState.OPEN, nextTimestamp));
                nextOpenDate = instrument.getMarket().getNextOpenDate(lastReadTs);
                open = true;
            } else if (lastReadTs.after(nextCloseDate)) {
                // Send close event
                instrument.getMarket().getMarketEventProvider().addEvent(new MarketEvent(MarketEvent.MarketState.CLOSED, nextTimestamp));
                nextCloseDate = instrument.getMarket().getNextCloseDate(lastReadTs);
                open = false;
            }
            if ("Trade".equals(lastRead[3])) {
                double ltp = lastTradePrice;
                if (lastRead[4].length() > 0) {
                    ltp = Double.parseDouble(lastRead[4]);
                }
                int ltq = lastTradeQty;
                if (lastRead[5].length() > 0) {
                    ltq = Integer.parseInt(lastRead[5]);
                }

                boolean farFrombidAsk = ltp <= lastBid - 1 || ltp >= lastAsk + 1;
                boolean farFromLast = Math.abs(ltp - lastTradePrice) > 2;
                boolean blockTrade = farFrombidAsk && farFromLast && Math.abs(ltq) > 1000;
                if (farFrombidAsk && farFromLast || blockTrade) {
                    if (open && !blockTrade) {
                        LOGGER.debug("Trade outside bid/ask - ignoring " + ltq + "@" + ltp + " - " + sdf.format(lastReadTs));
                    }
                } else {
                    lastTradePrice = ltp;
                    lastTradeQty = ltq;

                    PublicTradeUpdate.TradeSide side = null;
                    // Signing the trade - we must go back sometime in the past to make sure we don't mis-sign the trade
                    Iterator<OrderBookUpdate> updateIterator = lastOrderBookUpdates.iterator();

                    for (; updateIterator.hasNext();) {
                        OrderBookUpdate update = updateIterator.next();
                        OrderBook book = update.getBook();
                        if (book.getBidDepth() > 0 && book.getBid(0) == lastTradePrice && book.getBidQty(0) == lastTradeQty) {
                            side = PublicTradeUpdate.TradeSide.SELL;
                            break;
                        } else if (book.getAskDepth() > 0 && book.getAsk(0) == lastTradePrice && book.getAskQty(0) == lastTradeQty) {
                            side = PublicTradeUpdate.TradeSide.BUY;
                            break;
                        }
                        if (nextTimestamp - update.getTs() > 20000l) {
                            // keep this one, but remove the folowwing ones
                            break;
                        }
                    }

                    // Remove old entries
                    while (updateIterator.hasNext()) {
                        updateIterator.next();
                        updateIterator.remove();
                    }

                    if (side == null) {
                        double distFromBid = Math.abs(lastTradePrice - lastBid);
                        double distFromAsk = Math.abs(lastTradePrice - lastAsk);
                        // Signing the trade
                        side = distFromAsk < distFromBid ? PublicTradeUpdate.TradeSide.BUY : (distFromAsk > distFromBid ? PublicTradeUpdate.TradeSide.SELL : PublicTradeUpdate.TradeSide.UNKNOWN);
                    }
                    
                    PublicTradeUpdate update = new PublicTradeUpdate(instrument, lastTradeQty, lastTradePrice, PublicTradeUpdate.TradeType.MARKET, side, nextTimestamp);
                    instrument.getTradeEventProvider().addEvent(update);
                }
            } else if ("Quote".equals(lastRead[3])) {
                if (lastRead[7].length() > 0) {
                    lastBid = Double.parseDouble(lastRead[7]);
                }
                if (lastRead[8].length() > 0) {
                    lastBidQty = Integer.parseInt(lastRead[8]);
                }
                if (lastRead[9].length() > 0) {
                    lastAsk = Double.parseDouble(lastRead[9]);
                }
                if (lastRead[10].length() > 0) {
                    lastAskQty = Integer.parseInt(lastRead[10]);
                }
                OrderBook book = new OrderBook(lastBidQty != 0 ? 1 : 0,
                        new double[]{lastBid}, new int[]{lastBidQty},
                        lastAskQty > 0 ? 1 : 0,
                        new double[]{lastAsk}, new int[]{lastAskQty});
                OrderBookUpdate update = new OrderBookUpdate(instrument, book, nextTimestamp);
                lastOrderBookUpdates.push(update);
                instrument.getOrderBookProvider().addEvent(update);
            }

            nextTimestamp = -1;
            lastRead = null;
        }
    }

    public boolean hasNext() {
        return nextTimestamp() > 0;
    }

    public void read() {
        while (hasNext()) {
            readNext();
        }
    }
}
